package cms.rbac.service.implementation;

import cms.rbac.dao.AuthAssignmentDao;
import cms.rbac.dao.AuthItemChildDao;
import cms.rbac.dao.AuthItemDao;
import cms.rbac.entity.Item;
import cms.rbac.entity.Permission;
import cms.rbac.entity.Role;
import cms.rbac.service.RbacManager;
import cms.rbac.util.RbacException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;

@Service
public class DBRbacManager implements RbacManager {

    @Autowired
    public AuthItemDao authItemDao;

    @Autowired
    public AuthItemChildDao authItemChildDao;

    @Autowired
    public AuthAssignmentDao authAssignmentDao;

    public Role createRole(String name) {
        Role role = new Role();
        role.setName(name);
        return role;
    }

    public Permission createPermission(String name) {
        Permission permission = new Permission();
        permission.setName(name);
        return permission;
    }

    public boolean addRole(Role role) {
        return authItemDao.addItem(role);
    }

    public boolean addPermission(Permission permission) {
        return authItemDao.addItem(permission);
    }

    public boolean removeRole(Role role) {
        return role != null && authItemDao.deleteItemByName(role.getName());
    }

    public boolean removeAllRoles() {
        return authItemDao.deleteAllRoles();
    }

    public boolean removePermission(Permission permission) {
        return permission != null && authItemDao.deleteItemByName(permission.getName());
    }

    public boolean removeAllPermissions() {
        return authItemDao.deleteAllPermissions();
    }

    public int updateRole(Role role) {
        return role != null ? authItemDao.updateItem(role) : 0;
    }

    public int updatePermission(Permission permission) {
        return permission != null ? authItemDao.updateItem(permission) : 0;
    }

    public Role getRole(String name) throws RbacException {
        Role role = authItemDao.findRoleByName(name);
        if (role == null) {
            throw new RbacException("role of " + name + " can not found");
        }
        return role;
    }

    public Permission getPermission(String name) throws RbacException {
        Permission permission = authItemDao.findPermissionByName(name);
        if (permission == null) {
            throw new RbacException("permission of " + name + " can not found");
        }
        return permission;
    }


    public boolean addChild(Role parent, Permission child) {
        return addChildItem(parent, child);
    }

    public boolean addChild(Permission parent, Permission child) {
        return addChildItem(parent, child);
    }

    public boolean addChild(Role parent, Role child) {
        return addChildItem(parent, child);
    }

    public boolean removeChild(Role parent, Permission child) {
        return removeChildItem(parent, child);
    }

    public boolean removeChild(Permission parent, Permission child) {
        return removeChildItem(parent, child);
    }

    public boolean removeChild(Role parent, Role child) {
        return removeChildItem(parent, child);
    }

    public boolean removeChild(Role parent) {
        return removeChildItem(parent);
    }

    public boolean removeChild(Permission parent) {
        return removeChildItem(parent);
    }


    public boolean assign(Role role, int userId) {
        return authAssignmentDao.add(role.getName(), userId, System.currentTimeMillis());
    }

    public boolean assign(Permission permission, int userId) {
        return authAssignmentDao.add(permission.getName(), userId, System.currentTimeMillis());
    }

    public boolean revoke(Role role, int userId) {
        return authAssignmentDao.delete(userId, role.getName());
    }

    public boolean revoke(Permission permission, int userId) {
        return authAssignmentDao.delete(userId, permission.getName());
    }

    public boolean revokeAll(int userId) {
        return authAssignmentDao.deleteByUserId(userId);
    }

    public Role[] getRolesByUser(int userId) {
        return authAssignmentDao.findRolesByUserId(userId);
    }

    public Permission[] getPermissionsByRole(Role role) {
        ArrayList<String> permissions = getPermissionsNameByRole(role.getName());
        return authItemDao.findPermissionsByNames(permissions);
    }

    public Permission[] getPermissionsByUser(int userId) {
        ArrayList<String> result = getPermissionsNameByUser(userId);
        return authItemDao.findPermissionsByNames(result);
    }

    public boolean checkAccess(int userId, String permissionName) {
        ArrayList<String> permissions = getPermissionsNameByUser(userId);
        return permissions.contains(permissionName);
    }

    public boolean checkAccess(int userId, Permission permission) {
        return checkAccess(userId, permission.getName());
    }


    private ArrayList<String> getPermissionsNameByUser(int userId) {
        ArrayList<String> result = new ArrayList<String>();
        String[] roles = authAssignmentDao.findRolesNameByUserId(userId);
        String[] permissions = authAssignmentDao.findPermissionsNameByUserId(userId);
        for (String role : roles) {
            ArrayList<String> permissionsNameByRole = getPermissionsNameByRole(role);
            for (String permission : permissionsNameByRole) {
                result.add(permission);
            }
        }
        for (String permission : permissions) {
            result.add(permission);
            getTreePermissions(permission, result);
        }
        return result;
    }

    private ArrayList<String> getPermissionsNameByRole(String roleName) {
        ArrayList<String> roles = new ArrayList<String>();
        roles.add(roleName);
        getTreeRoles(roleName, roles);

        ArrayList<String> permissions = new ArrayList<String>();
        for (String role : roles) {
            String[] children = authItemChildDao.findChildrenNameByType(role, Item.TYPE_PERMISSION);
            for (String permission : children) {
                permissions.add(permission);
                getTreePermissions(permission, permissions);
            }
        }
        return permissions;
    }


    private boolean detectLoop(String parent, String child) {
        if (child.equals(parent)) {
            return true;
        }
        for (String item : getChildrenOnce(child)) {
            if (detectLoop(parent, item)) {
                return true;
            }
        }
        return false;
    }

    private void getTreePermissions(String permission, ArrayList<String> result) {
        String[] childrenName = authItemChildDao.findChildrenNameByType(permission, Item.TYPE_PERMISSION);
        if (childrenName.length > 0) {
            for (String name : childrenName) {
                result.add(name);
                getTreePermissions(name, result);
            }
        }
    }

    private void getTreeRoles(String role, ArrayList<String> result) {
        String[] childrenName = authItemChildDao.findChildrenNameByType(role, Item.TYPE_ROLE);
        if (childrenName.length > 0) {
            for (String name : childrenName) {
                result.add(name);
                getTreeRoles(name, result);
            }
        }
    }

    private String[] getChildrenOnce(String name) {
        return authItemChildDao.findChildrenName(name);
    }

    private boolean addChildItem(Item parent, Item child) {
        return !(parent == null || child == null) && !detectLoop(parent.getName(), child.getName()) && authItemChildDao.addChild(parent.getName(), child.getName());
    }

    private boolean removeChildItem(Item parent, Item child) {
        return !(parent == null || child == null) && authItemChildDao.delete(parent.getName(), child.getName());
    }

    private boolean removeChildItem(Item parent) {
        return !(parent == null) && authItemChildDao.deleteAllChildByParent(parent.getName());
    }
}
